#!/usr/bin/env python

import sys, os, popen2, select, fcntl, signal, math, datetime

class ReadLinePoll:
    def __init__(self):
        self.poll = select.poll()
        self.cb = {}
        self.bf = {}

    def register(self, fd, cb):
        self.poll.register(fd, select.POLLIN)
        fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
        self.cb[fd] = cb
        self.bf[fd] = ""

    def unregister(self, fd):
        self.poll.unregister(fd)
        del self.cb[fd]
        del self.bf[fd]

    def start(self):
        while self.cb:
            ev_list = self.poll.poll()
            for fd, ev in ev_list:
                if ev & select.POLLIN == 0:
                    self.cb[fd](None)
                    self.unregister(fd)
                    continue
                bf = self.bf[fd] + os.read(fd, 2**16)
                lines = bf.split('\n')
                self.bf[fd] = lines.pop()
                for l in lines:
                    self.cb[fd](l)

    def stop(self):
        for fd in self.cb.keys():
            self.unregister(fd)

class RandomVariable(list):
    def __init__(self, init=[]):
        list.__init__(self, init)

    def __iadd__(self, val):
        self.append(val)
        return self

    def __float__(self):
        return self.exp()

    def __str__(self):
        return "sum %6.2f avg %6.2f -/+ %6.2f %6.2f%% /%d" % (self.sum(), self.exp(),
                self.dia(), self.var(), len(self))

    def sum(self):
        return sum(self)

    def exp(self):
        return sum(self)/len(self)

    def dev(self):
        return math.sqrt(reduce(lambda x,y:x+y*y, self, 0)/len(self)
                - self.exp()**2)

    def dia(self):
        return (max(self)-min(self))/2

    def var(self):
        return self.dev()/(self.exp() or 1)*100

class IoopStat:
    def __init__(self, init=[[],[],[],[],[],[],[],[]]):
        self.ef = RandomVariable(init[0])
        self.sp = RandomVariable(init[1])
        self.ex = RandomVariable(init[2])
        self.de = RandomVariable(init[3])
        self.ef_var = RandomVariable(init[4])
        self.sp_var = RandomVariable(init[5])
        self.ex_var = RandomVariable(init[6])
        self.de_var = RandomVariable(init[7])

    def __add__(self, st):
        self.ef += st.ef.sum()
        self.sp += st.sp.sum()
        self.ex += st.ex.exp()
        self.de += st.de.exp()
        self.ef_var += st.ef.var()
        self.sp_var += st.sp.var()
        self.ex_var += st.ex.var()
        self.de_var += st.de.var()
        return self

    def __str__(self):
        return "ef: %(ef)s (%(ef_var)s)\nsp: %(sp)s (%(sp_var)s)\n" \
               "ex: %(ex)s (%(ex_var)s)\nde: %(de)s (%(de_var)s)" % vars(self)

class Log:
    def __init__(self, name):
        self.name = name

    def start(self):
        self.log_f = open(self.name + '.log', 'a', 0)
        self.err_f = open(self.name + '.err', 'a', 0)
        self.res_f = open(self.name + '.res', 'a', 0)
        self.res("-------------------- %s --------------------",
                datetime.datetime.now())

    def stop(self):
        self.log_f.close()
        self.err_f.close()
        self.res_f.close()

    def log(self, fmt, *arg):
        self.log_f.write(fmt % arg + '\n')
        print fmt % arg

    def err(self, fmt, *arg):
        self.err_f.write(fmt % arg + '\n')
        print "ERR", fmt % arg

    def res(self, fmt, *arg):
        self.res_f.write(fmt % arg + '\n')
        self.log(fmt, *arg)


class Ioop:
    def __init__(self, poll, log, cmd, name):
        self.poll = poll
        self.log = log
        self.cmd = cmd
        self.name = name
        self.stat = IoopStat()

    def on_update(self, src, res):
        pass

    def on_complete(self, src, res):
        pass

    def start(self):
        self.child = popen2.Popen3(self.cmd, True)
        self.poll.register(self.child.fromchild.fileno(), self.output_cb)
        self.poll.register(self.child.childerr.fileno(), self.error_cb)
        self.log.log('exec %d: %s', self.child.pid, ' '.join(self.cmd))

    def stop(self):
        try:
            os.kill(signal.SIGTERM, self.child.pid)
        except:
            pass

    def error_cb(self, line):
        if line == None:
            return
        self.log.err("%d %s", self.child.pid, line)

    def output_cb(self, line):
        if line == None:
            self.on_complete(self, self.stat)
            return
        ef, sp, ex, de = map(float, line.split())
        self.res = IoopStat([[ef/2**20], [sp/2**20], [ex*1000], [de*1000],
                             [0], [0], [0], [0]])
        self.stat += self.res
        self.on_update(self, self.res)

class ParaIoop:
    def __init__(self, poll, log, work):
        self.ts = []
        self.re = []
        self.st = []
        self.gr = []
        self.poll = poll
        self.log = log
        self.stat = IoopStat()
        self.passno = 0
        for name, cnt, cmd in work:
            gr = []
            for i in range(cnt):
                fn = "%s_%d" % (name, i)
                inst = Ioop(self.poll, self.log, cmd + [fn], fn)
                inst.idx = len(self.ts)
                gr.append(inst)
                inst.on_update = self.update_cb
                inst.on_complete = self.complete_cb
                self.ts.append(inst)
                self.re.append(None)
                self.st.append(None)
            self.gr.append((name, gr, IoopStat()))

    def start(self):
        for t in self.ts:
            t.start()

    def stop(self):
        for t in self.ts:
            t.stop()

    def update_cb(self, src, res):
        self.re[src.idx] = res
        self.on_update_one(self, src, res)
        if reduce(lambda a,b:b and a, self.re, True):
            self.on_update(self, self.re)
            self.re = [None]*len(self.ts)

    def complete_cb(self, src, stat):
        self.st[src.idx] = stat
        self.on_complete_one(self, src, stat)
        if reduce(lambda a,b:b and a, self.st, True):
            self.on_complete(self, self.st)

    def on_complete(self, src, stat):
        pass

    def on_complete_one(self, src, one, stat):
        pass

    def on_update_one(self, src, one, res):
        pass

    def on_update(self, src, res):
        self.passno += 1
        self.log.log("----- pass %d -----", self.passno)
        stat = IoopStat()
        for name, ts, gst in self.gr:
            st = IoopStat()
            for t in ts:
                self.log.log("%s\n%s", t.name, t.res)
                st += t.res
            self.log.log("%s\n%s", name, st)
            stat += st
            gst += st
        self.log.log("%s\n%s", "TOTAL", stat)
        self.stat += stat

    def result(self):
        for name, ts, gst in self.gr:
            self.log.res("%s\n%s", name, gst)
        self.log.res("%s\n%s", "RESULT", self.stat)

def run(work, log):
    log.start()
    log.res("ker: %s", os.popen("uname -a").read()[:-1])
    log.res("arg: %s", " ".join(arg[:-1]))

    poll = ReadLinePoll()
    test = ParaIoop(poll, log, work)

    try:
        test.start()
        poll.start()
    except KeyboardInterrupt:
        pass

    test.stop()
    poll.stop()

    test.result()
    log.stop()

def main(arg):
    if not arg:
        print """
usage   : paraioop suffix [ -- name count prefix ]
example : paraioop ./ioop -D -w -i -s 1g -r 64k -- user 2 -- root 1 sudo
"""
        return
    arg.append('--')
    idx = arg.index('--')
    cmd = arg[:idx]
    work = []
    while idx < len(arg)-1:
        end = arg.index('--', idx+1)
        name = arg[idx+1]
        count = int(arg[idx+2])
        prefix = arg[idx+3:end]
        work.append((name, count, prefix + cmd))
        idx = end
    log = Log('paraioop')
    run(work, log)

if __name__ == '__main__':
    main(sys.argv[1:])
